﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Text;
using System.Windows.Forms;

using FWF.Logging.Viewer.Style;

namespace FWF.Logging.Viewer
{
    /// <summary>
    /// 
    /// </summary>
    public partial class LogGrid : UserControl
    {

        private string _searchText;
        private bool _searchIgnoreCase = true;
        private FindLocation _findLocation;

        private BindingSource _bindingSource;
        private readonly DataTable _dataTable = new DataTable( "Messages" );

        private readonly List<LogFilterItem> _filters = new List<LogFilterItem>();

        private struct FindLocation
        {
            public int Row;
            public int Column;

            public void Reset()
            {
                Row = 0;
                Column = 0;
            }
        }

        #region Constructors / Finalier

        /// <summary>
        /// Initializes a new instance of the <see cref="LogGrid"/> class.
        /// </summary>
        public LogGrid()
        {
            InitializeComponent();

            //
            WindowStyleManager.ApplyStyle( this );
        }

        #endregion

        #region Form Events

        /// <summary>
        /// Handles the Load event of the MessagingLogGrid control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void LogGrid_Load( object sender, EventArgs e )
        {
            //
            LogEventItem.PopulateDataTable( _dataTable );

            //
            _bindingSource = new BindingSource( _dataTable, null );

            //
            _bindingSource.Filter = GetFilterExpression();

            //
            gridMessages.DataSource = _bindingSource;

            // Apply default sort by date time
            _bindingSource.Sort = "TimeStamp";
        }

        #endregion

        #region Grid Events
        
        /// <summary>
        /// Handles the CellFormatting event of the gridMessages control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Forms.DataGridViewCellFormattingEventArgs"/> instance containing the event data.</param>
        private void gridMessages_CellFormatting( object sender, DataGridViewCellFormattingEventArgs e )
        {
            if ( e.RowIndex == -1 )
            {
                // Exclude when formatting a column header
                return;
            }

            //
            string loglevel = gridMessages.Rows[ e.RowIndex ].Cells[ 6 ].Value as string;
            string ex = gridMessages.Rows[ e.RowIndex ].Cells[ 16 ].Value as string;

            if ( !string.IsNullOrEmpty( ex ) )
            {
                e.CellStyle.ForeColor = Color.Red;
            }
            else
            {
                switch (loglevel)
                {
                    case "INFO":
                        e.CellStyle.ForeColor = Color.DarkGreen;
                        break;
                    case "DEBUG":
                        e.CellStyle.ForeColor = Color.Black;
                        break;
                    case "WARN":
                        e.CellStyle.ForeColor = Color.DarkOrange;
                        break;
                    case "ERROR":
                    case "FATAL":
                    case "EMERGENCY":
                        e.CellStyle.ForeColor = Color.Red;
                        break;
                }
            }

        }

        /// <summary>
        /// Handles the CellMouseDown event of the gridMessages control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Forms.DataGridViewCellMouseEventArgs"/> instance containing the event data.</param>
        private void gridMessages_CellMouseDown( object sender, DataGridViewCellMouseEventArgs e )
        {
            if ( e.RowIndex == -1 )
            {
                return;
            }
            if ( e.ColumnIndex == -1 )
            {
                return;
            }

            // Ensure that focus is changed to the current cell when clicking with the mouse, this
            // will allow the proper context menus to reference .CurrentCell
            gridMessages.CurrentCell = gridMessages.Rows[ e.RowIndex ].Cells[ e.ColumnIndex ];
        }

        #endregion
        
        #region Context Menu Events

        /// <summary>
        /// Handles the Click event of the mnuCopy control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void mnuCopy_Click( object sender, EventArgs e )
        {
            Clipboard.SetText(
                gridMessages.CurrentCell.Value as string
                );
        }

        /// <summary>
        /// Handles the Click event of the openInNotepadToolStripMenuItem control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void openInNotepadToolStripMenuItem_Click( object sender, EventArgs e )
        {
            string filePath = Path.GetTempFileName();

            //
            File.WriteAllText( 
                filePath,
                gridMessages.CurrentCell.Value as string
                );

            //
            Process.Start( "notepad.exe", "\"" + filePath + "\"" );
        }

        #endregion

        #region Drag and Drop

        /// <summary>
        /// Handles the DragDrop event of the gridMessages control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Forms.DragEventArgs"/> instance containing the event data.</param>
        private void gridMessages_DragDrop( object sender, DragEventArgs e )
        {
            OnDragDrop( e );
        }

        /// <summary>
        /// Handles the DragEnter event of the gridMessages control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Forms.DragEventArgs"/> instance containing the event data.</param>
        private void gridMessages_DragEnter( object sender, DragEventArgs e )
        {
            if ( e.Data.GetDataPresent( DataFormats.FileDrop ) )
            {
                e.Effect = DragDropEffects.Copy;
            }
            else
            {
                e.Effect = DragDropEffects.None;
            }
        }

        #endregion

        /// <summary>
        /// Clears this instance.
        /// </summary>
        public void Clear()
        {
            _dataTable.Rows.Clear();
        }

        /// <summary>
        /// Currents the cell value.
        /// </summary>
        /// <returns></returns>
        public string GetCurrentCellValue()
        {
            if ( InvokeRequired )
            {
                return string.Empty;
            }

            return gridMessages.CurrentCell.Value as string;
        }

        /// <summary>
        /// Gets the items.
        /// </summary>
        /// <value>The items.</value>
        public IEnumerable<LogEventItem> Items
        {
            get
            {
                foreach ( DataRow row in _dataTable.Rows )
                {
                    yield return LogEventItem.FromDataRow(row);
                }
            }
        }

        /// <summary>
        /// Edits the filters.
        /// </summary>
        public void EditFilters()
        {
            if ( InvokeRequired )
            {
                Invoke( new MethodInvoker( EditFilters ) );
                return;
            }

            using (var frm = new frmLogFilter())
            {
                frm.Filters.Clear();
                frm.Filters.AddRange( _filters );

                DialogResult result = frm.ShowDialog( this );

                if ( result == DialogResult.OK )
                {
                    IEnumerable<LogFilterItem> listFilters = frm.GetFilters();

                    _filters.Clear();

                    if ( listFilters.GetEnumerator().MoveNext() )
                    {
                        _filters.AddRange( listFilters );
                    }

                    //
                    _bindingSource.Filter = GetFilterExpression();
                }
            }
        }

        /// <summary>
        /// Logs the message.
        /// </summary>
        /// <param name="itm">The itm.</param>
        public void LogMessage(LogEventItem itm)
        {
            DataRow row = itm.ToDataRow( _dataTable );

            // Add to display
            _dataTable.Rows.Add( row );
        }

        /// <summary>
        /// Gets the filter expression.
        /// </summary>
        /// <returns></returns>
        private string GetFilterExpression()
        {
            if ( _filters.Count == 0 )
            {
                return null;
            }

            StringBuilder builder = new StringBuilder();

            _filters.ForEach(
                x =>
                {
                    if (
                        !string.IsNullOrEmpty(x.Category) &&
                        !string.IsNullOrEmpty( x.LogPrefix )
                        )
                    {
                        builder.Append("LogPrefix + '.' + Category <> '" + x.Category + "." + x.LogPrefix + "' and ");
                    }
                    else if (!string.IsNullOrEmpty( x.Category ))
                    {
                        builder.Append( "Category <> '" + x.Category + "' and " );
                    }
                    else if (!string.IsNullOrEmpty(x.LogPrefix))
                    {
                        builder.Append("LogPrefix <> '" + x.LogPrefix + "' and ");
                    }
                }
                );

            if ( builder.Length > 0 )
            {
                builder.Length = builder.Length - " and ".Length;
            }

            return builder.ToString();
        }

        /// <summary>
        /// Handles the KeyDown event of the txtMessages control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Forms.KeyEventArgs"/> instance containing the event data.</param>
        private void LogGrid_KeyDown( object sender, KeyEventArgs e )
        {
            bool bIsFindKeystroke = ( e.Control && e.KeyCode == Keys.F );
            bool bIsFindNextKeystroke = e.KeyCode == Keys.F3;

            if ( bIsFindKeystroke )
            {
                e.Handled = true;

                // No reason to find if no records exist
                if ( gridMessages.Rows.Count == 0 )
                {
                    return;
                }

                using ( frmFind frm = new frmFind() )
                {
                    frm.FindText = _searchText;
                    frm.IgnoreCase = _searchIgnoreCase;

                    DialogResult result = frm.ShowDialog( this );

                    if ( result == DialogResult.OK )
                    {
                        //
                        _searchText = frm.FindText;
                        _searchIgnoreCase = frm.IgnoreCase;

                        //
                        if ( gridMessages.SelectedCells.Count > 0 )
                        {
                            _findLocation.Row = gridMessages.SelectedCells[ 0 ].RowIndex;
                            _findLocation.Column = gridMessages.SelectedCells[ 0 ].ColumnIndex;
                        }
                        else
                        {
                            _findLocation.Reset();
                        }

                        //
                        PerformFind();
                    }
                }
            }

            if ( bIsFindNextKeystroke )
            {
                e.Handled = true;

                // No reason to find if no records exist
                if ( gridMessages.Rows.Count == 0 )
                {
                    return;
                }

                // Perform find in next column or row
                if ( ( _findLocation.Column + 1 ) >= gridMessages.Columns.Count )
                {
                    // Start search on next row
                    _findLocation.Row++;
                    _findLocation.Column = 0;
                }
                else
                {
                    // Start searching on next column
                    _findLocation.Column++;
                }

                //
                //using ( frmFind frm = new frmFind() )
                //{
                    //
                    PerformFind();
                //}
            }
        }

        /// <summary>
        /// Performs the find.
        /// </summary>
        private void PerformFind()
        {
            // NOTE: Starting at row x, column y, cursor z, find a matching string

            if ( _findLocation.Row > gridMessages.Rows.Count )
            {
                _findLocation.Reset();
            }

            //
            for ( int x = _findLocation.Row; x < gridMessages.Rows.Count; x++ )
            {
                DataGridViewRow row = gridMessages.Rows[ x ];

                for ( int y = _findLocation.Column; y < row.Cells.Count; y++ )
                {
                    DataGridViewCell cell = row.Cells[ y ];

                    string cellValue = null;

                    if ( !object.ReferenceEquals( cell.Value, null ) )
                    {
                        cellValue = cell.Value.ToString();
                    }

                    if ( string.IsNullOrEmpty( cellValue ) )
                    {
                        continue;
                    }

                    //
                    bool cellValueMatch = false;

                    // Search differently based upon ignoring case
                    if ( _searchIgnoreCase )
                    {
                        cellValueMatch = ( cellValue.ToLower().IndexOf( _searchText.ToLower() ) > -1 );
                    }
                    else
                    {
                        cellValueMatch = cellValue.Contains( _searchText );
                    }

                    if ( cellValueMatch )
                    {
                        // Set the location of the match
                        _findLocation.Row = x;
                        _findLocation.Column = y;
 
                        // TODO: Hightlight cell
                        cell.Selected = true;
                        return;
                    }
                }

                // If there is no match for the row, reset the column index
                _findLocation.Column = 0;
            }

            DialogResult result = MessageBox.Show(
                this,
                "Unable to find '" + _searchText + "'.  Start from the beginning?",
                "Find",
                MessageBoxButtons.YesNoCancel
                );

            if ( result == DialogResult.Yes )
            {
                _findLocation.Reset();

                PerformFind();
            }
        }
    }
}
